package com.wut.redis.lock.aspect;

import com.wut.redis.lock.annotation.RedisLock;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;

/**
 * 分布式锁AOP
 */
@Slf4j
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RedisLockAspect {

    private final ExpressionParser parser = new SpelExpressionParser();
    private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    @Autowired
    private RedisLockRegistry redisLockRegistry;

    @Pointcut("@annotation(com.wut.redis.lock.annotation.RedisLock)")
    private void lockPoint() {
        // lockPoint
    }

    @Around("lockPoint()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        String key = redisLock.key();
        Object[] args = pjp.getArgs();
        key = parse(key, method, args);

        //得到锁,执行方法，释放锁
        log.info("Obtain lock: " + key);
        Lock lock = redisLockRegistry.obtain(key);
        if (redisLock.waitMills() == -1) {
            lock.lock();
            //为了妥协阿里代码规约，特增加冗余代码
            try {
                return pjp.proceed();
            } catch (Exception e) {
                log.error("Execute locked method occured an exception", e);
                throw e;
            } finally {
                lock.unlock();
                log.info("Release lock: " + key);
            }
        } else if ((redisLock.waitMills() == 0)) {
            boolean flag = lock.tryLock();
            if (!flag) {
                log.info("Obtain lock failed: " + key);
                return null;
            }
        } else {
            boolean flag = lock.tryLock(redisLock.waitMills(), TimeUnit.MILLISECONDS);
            if (!flag) {
                log.info("Obtain lock failed : " + key);
                return null;
            }
        }

        return proceed(pjp, key, lock);
    }

    private Object proceed(ProceedingJoinPoint pjp, String key, Lock lock) throws Throwable {
        try {
            return pjp.proceed();
        } catch (Exception e) {
            log.error("Execute locked method occured an exception", e);
            throw e;
        } finally {
            lock.unlock();
            log.info("Release lock: " + key);
        }
    }

    private String parse(String key, Method method, Object[] args) {
        String[] params = discoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < Objects.requireNonNull(params).length; i++) {
            context.setVariable(params[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }
}
